package app

import (
	"barge/pkg/manager"
	"barge/pkg/sshx"
	"barge/pkg/transporter"
	"barge/pkg/uix"
	"bytes"
	"fmt"
	"github.com/pkg/errors"
	"github.com/rs/zerolog/log"
	"github.com/spf13/viper"
	"io"
	"os"
	"time"
)

type RunOption struct {
	Quiet            bool
	Center           string
	CenterHost       string
	CenterPort       int
	CenterUsername   string
	CenterPassword   string
	Registry         string
	RegistryAddr     string
	RegistryUsername string
	RegistryPassword string
	Architectures    []string
	Images           []string
	Skip             []string
	OnlyPush         bool
	ImageFeature     []manager.ImageFeature
}

type Runner struct {
	*RunOption
	Center   *manager.Center
	Registry *manager.Registry
	Images   []*manager.MultiArchImage
	Rules    []*manager.Rule
	tr       *transporter.Transporter
}

func NewRunner(opt *RunOption) (*Runner, error) {
	r := &Runner{
		RunOption: opt,
	}
	err := r.load()
	if err != nil {
		return nil, err
	}
	return r, nil
}

// load 加载配置与指定的参数
func (r *Runner) load() error {
	var center *manager.Center
	var registry *manager.Registry
	if r.RunOption.Center == "localhost" {
		r.RunOption.Center = ""
		r.RunOption.CenterHost = "localhost"
	}

	m, err := NewConfigManager(viper.GetString("configPath"))
	if err != nil {
		return err
	}
	r.Rules = m.Rules

	if r.RunOption.Center != "" {
		center = m.GetCenterByName(r.RunOption.Center)
		if center == nil {
			return errors.New("中转服务器名称不存在")
		}
	} else {
		p := manager.Password{Raw: r.RunOption.CenterPassword}
		p.Action(r.Rules)
		center = &manager.Center{
			Name:     time.Now().String(),
			Host:     r.RunOption.CenterHost,
			Port:     r.RunOption.CenterPort,
			Username: r.RunOption.CenterUsername,
			Password: p,
		}
	}

	if r.RunOption.Registry != "" {
		registry = m.GetRegistryByName(r.RunOption.Registry)
		if registry == nil {
			return errors.New("镜像仓库名称不存在")
		}
		if r.RunOption.Architectures != nil {
			registry.Architectures = r.RunOption.Architectures
		}
	} else {
		p := manager.Password{Raw: r.RunOption.RegistryPassword}
		p.Action(r.Rules)
		registry = &manager.Registry{
			Name:          time.Now().String(),
			Address:       r.RunOption.RegistryAddr,
			Username:      r.RunOption.RegistryUsername,
			Password:      p,
			Architectures: r.RunOption.Architectures,
		}
	}

	if center.Username != "" && center.Password.Raw == "" {
		center.Password.Raw = uix.Input("请输入中转服务器密码：")
	}
	r.Center = center
	if registry.Username != "" && registry.Password.Raw == "" {
		registry.Password.Raw = uix.Input("请输入镜像仓库密码：")
	}
	r.Registry = registry
	images := make([]*manager.MultiArchImage, 0)
	for _, imageStr := range r.RunOption.Images {
		image := manager.NewMultiArchImage(imageStr, r.Registry.Architectures, r.RunOption.ImageFeature)
		images = append(images, image)
	}
	r.Images = images
	err = r.initTransporter()
	if err != nil {
		return err
	}
	return nil
}

func (r *Runner) initTransporter() error {
	var err error
	var stdio io.Writer = os.Stdout
	var stderr io.Writer = os.Stderr
	if r.Quiet {
		stdio = new(bytes.Buffer)
		stderr = new(bytes.Buffer)
	}
	var executor transporter.Executor
	var opt *transporter.Option
	if r.Center.IsLocal() {
		opt = &transporter.Option{
			Quiet:    r.Quiet,
			Stdout:   stdio,
			Stderr:   stderr,
			Center:   r.Center,
			Registry: r.Registry,
		}
		executor = transporter.NewLocalExecutor()
	} else {
		opt = &transporter.Option{
			Quiet:    r.Quiet,
			Stdout:   stdio,
			Stderr:   stderr,
			Center:   r.Center,
			Registry: r.Registry,
		}
		sshOpt := &sshx.Option{
			Addr:     opt.Center.Addr(),
			Username: opt.Center.Username,
			Password: opt.Center.Password.Raw,
		}
		executor, err = transporter.NewRemoteExecutor(sshOpt)
		if err != nil {
			return err
		}
	}
	r.tr = transporter.NewTransporter(opt, executor)
	return nil
}

func (r *Runner) Run() (err error) {
	for _, image := range r.Images {
		if r.OnlyPush {
			err = r.push(image)
			if err != nil {
				return
			}
			continue
		}
		err = r.transOneImage(image)
		if err != nil {
			return
		}
	}
	return nil
}

func (r *Runner) push(image *manager.MultiArchImage) error {
	if !image.IsSingleArch() {
		return errors.New("本地推送镜像不支持多架构")
	}
	return r.tr.Push(image.GetFirstImage())
}

func (r *Runner) transOneImage(image *manager.MultiArchImage) (err error) {
	image.Action(r.Rules)
	var relativeImageNames []string
	relativeImageNames, err = r.tr.Transport(image)
	if err != nil {
		return err
	}
	if len(relativeImageNames) == 0 {
		return nil
	}
	fmt.Println("")
	fmt.Println("**********bundle*********")
	fmt.Printf("镜像 %v 的 %v 子镜像：\n", image.Reference, len(relativeImageNames))
	for _, imageName := range relativeImageNames {
		fmt.Printf("\n\t%v", imageName)
	}
	fmt.Println()
	fmt.Println()
	fmt.Println("**********bundle*********")
	fmt.Println("")
	for _, imageName := range relativeImageNames {
		relativeImage := manager.NewMultiArchImage(imageName, image.Architectures, nil)
		if r.isSkipImage(relativeImage) {
			fmt.Println("")
			log.Info().Str("skip", relativeImage.Reference.String()).Msg("跳过镜像")
			continue
		}
		err = r.transOneImage(relativeImage)
		if err != nil {
			return
		}
	}
	return nil
}

func (r Runner) isSkipImage(image *manager.MultiArchImage) bool {
	for _, skip := range r.Skip {
		if image.GetFirstImage().Reference.Application() == skip {
			return true
		}
	}
	return false
}
